package org.visionlibrary.image.model;

import java.awt.image.ColorModel;

import javax.media.jai.TiledImage;

public class FilterTools {
	static public double[] getNormalizedHistogram(int[] histogram, int numOfPixels) {
		double[] h = new double[histogram.length];
		for (int i = 0; i < h.length; i++) {
			h[i] = (double)histogram[i]/(double)numOfPixels;
		}
		
		return h;
	}
	
	static public double[] getCumulativeHistogramDouble(double[] histogram) {
		double[] chistogram = new double[histogram.length];
		
		for(int i=0; i < chistogram.length; i++) {
			for(int j=0; j <= i; j++) {
				chistogram[i] += histogram[j];
			}
		}
		
		return chistogram;
	}
	
	static public int[] getCumulativeHistogram(TiledImage src, int channel) {
		int[] histogram = getHistogram(src, channel);
		int[] chistogram = new int[histogram.length];
		
		for(int i=0; i < chistogram.length; i++) {
			for(int j=0; j <= i; j++) {
				chistogram[i] += histogram[j];
			}
		}
		
		return chistogram;
	}
	
	static public int[] getHistogram(TiledImage src, int channel) {
		ColorModel cm = src.getColorModel();
		int componentSize = cm.getComponentSize(channel);
		int histLength = (int)Math.pow(2, componentSize);
		
		int[] histogram = new int[histLength];
		for (int i = 0; i < histLength; i++)
			histogram[i] = 0;

		for (int y = 0; y < src.getHeight(); y++)
			for (int x = 0; x < src.getWidth(); x++)
				histogram[src.getSample(x, y, channel)]++;

		return histogram;
	}

	static float[][] getFloat(TiledImage in, int channel) {
		int width = in.getWidth();
		int height = in.getHeight();
		float[][] out = new float[height][width];
		for (int i = 0; i < height; i++)
			for (int j = 0; j < width; j++)
				out[i][j] = (float) in.getSample(i, j, channel);
		return out;
	}

	static float[][] simpleDCT(float[][] in) {
		int N = in.length;
		int M = in[0].length;
		float[][] out = new float[N][M];

		float piN1 = (float) Math.PI / (float) N;
		float piN2 = (float) Math.PI / (float) M;

		for (int k1 = 0; k1 < N; k1++)
			for (int k2 = 0; k2 < M; k2++) {
				float temp = 0.0f;
				for (int n1 = 0; n1 < N; n1++)
					for (int n2 = 0; n2 < M; n2++)
						temp += in[n1][n2]
								* (float) Math.cos(piN1 * (float) k1
										* ((float) n1 + 0.5f))
								* (float) Math.cos(piN2 * (float) k2
										* ((float) n2 + 0.5f));
				out[k1][k2] = temp;
			}

		return out;
	}

	static public Complex[] simpleDFT(Complex[] in) {
		int N = in.length;
		Complex[] out = new Complex[N];

		for (int k = 0; k < N; k++) {
			Complex complex = new Complex();
			for (int i = 0; i < N; i++) {
				double arg = 2.0 * Math.PI * (double) (k * i) / (double) N;
				complex = complex.plus(in[i].times(new Complex((float) Math
						.cos(arg), (float) -Math.sin((double) arg))));
			}
			out[k] = complex;
		}
		return out;
	}

	static public Complex[] inverseDFT(Complex[] in) {
		int N = in.length;
		float normalizationFactor = (float) (1.0 / ((double) N));
		Complex[] out = new Complex[N];

		for (int k = 0; k < N; k++) {
			Complex complex = new Complex();
			for (int i = 0; i < N; i++) {
				double arg = 2.0 * Math.PI * (double) (k * i) / (double) N;
				complex = complex.plus(in[i].times(new Complex((float) Math
						.cos(arg), (float) Math.sin((double) arg))));
			}
			out[k] = complex.times(normalizationFactor);
		}
		return out;
	}

	static public Complex[][] simpleDFT(Complex[][] in) {
		int N = in.length;
		int M = in[0].length;
		Complex[][] out = new Complex[N][M];

		for (int i = 0; i < N; i++)
			for (int k = 0; k < M; k++) {
				Complex complex = new Complex();
				;
				for (int m = 0; m < M; m++)
					for (int n = 0; n < N; n++) {
						Complex tmp = in[m][n];
						double arg1 = 2.0 * Math.PI * (double) (m * i)
								/ (double) M;
						double arg2 = 2.0 * Math.PI * (double) (n * k)
								/ (double) N;
						tmp = tmp.times(new Complex((float) Math.cos(arg1),
								(float) -Math.sin((double) arg1)));
						tmp = tmp.times(new Complex((float) Math.cos(arg2),
								(float) -Math.sin((double) arg2)));
						complex = complex.plus(tmp);
					}
				out[i][k] = complex;
			}
		return out;
	}

	static public Complex[][] inverseDFT(Complex[][] in) {
		int N = in.length;
		int M = in[0].length;
		float normalizationFactor = (float) (1.0 / ((double) (N * M)));
		Complex[][] out = new Complex[N][M];

		for (int i = 0; i < N; i++)
			for (int k = 0; k < M; k++) {
				Complex complex = new Complex();
				;
				for (int m = 0; m < M; m++)
					for (int n = 0; n < N; n++) {
						Complex tmp = in[m][n];
						double arg1 = 2.0 * Math.PI * (double) (m * i)
								/ (double) M;
						double arg2 = 2.0 * Math.PI * (double) (n * k)
								/ (double) N;
						tmp = tmp.times(new Complex((float) Math.cos(arg1),
								(float) Math.sin((double) arg1)));
						tmp = tmp.times(new Complex((float) Math.cos(arg2),
								(float) Math.sin((double) arg2)));
						complex = complex.plus(tmp);
					}
				out[i][k] = complex.times(normalizationFactor);
			}
		return out;
	}

	static public Complex[] simpleFFT(Complex[] in) {
		int N = in.length;

		if (N == 1)
			return new Complex[] { new Complex(in[0].re(), in[0].im()) };

		int mask = 1;
		int bitsSet = 0;
		while (mask <= N) {
			if ((N & mask) > 0)
				bitsSet++;
			mask <<= 1;
		}

		if (bitsSet > 1)
			throw new RuntimeException("N is not a power of 2");

		Complex[] even = new Complex[N / 2];
		Complex[] odd = new Complex[N / 2];
		for (int i = 0; i < N / 2; i++) {
			even[i] = in[2 * i];
			odd[i] = in[2 * i + 1];
		}

		even = simpleFFT(even);
		odd = simpleFFT(odd);

		// combine
		Complex[] out = new Complex[N];
		for (int k = 0; k < N / 2; k++) {
			float kth = -2.0f * (float) k * (float) Math.PI / (float) N;
			Complex wk = new Complex((float) Math.cos(kth), (float) Math
					.sin(kth));
			out[k] = even[k].plus(wk.times(odd[k]));
			out[k + N / 2] = even[k].minus(wk.times(odd[k]));
		}
		return out;
	}

	static public Complex[] inverseFFT(Complex[] in) {
		int N = in.length;
		float normalizationFactor = (float) (1.0 / ((double) N));
		Complex[] out = new Complex[N];

		for (int i = 0; i < N; i++)
			out[i] = in[i].conjugate();

		out = simpleFFT(out);

		for (int i = 0; i < N; i++)
			out[i] = out[i].conjugate().times(normalizationFactor);

		return out;
	}

	static public Complex[][] simpleFFT(Complex[][] in) {
		int N = in.length;
		int M = in[0].length;

		Complex[][] out = new Complex[N][M];

		for (int i = 0; i < N; i++)
			out[i] = simpleFFT(in[i]);

		for (int i = 0; i < M; i++) {
			Complex[] tmp = new Complex[N];
			for (int j = 0; j < N; j++)
				tmp[j] = out[j][i];
			tmp = simpleFFT(tmp);
			for (int j = 0; j < N; j++)
				out[j][i] = tmp[j];
		}

		return out;
	}

	static public Complex[][] inverseFFT(Complex[][] in) {
		int N = in.length;
		int M = in[0].length;

		Complex[][] out = new Complex[N][M];

		for (int i = 0; i < N; i++)
			out[i] = inverseFFT(in[i]);

		for (int i = 0; i < M; i++) {
			Complex[] tmp = new Complex[N];
			for (int j = 0; j < N; j++)
				tmp[j] = out[j][i];
			tmp = inverseFFT(tmp);
			for (int j = 0; j < N; j++)
				out[j][i] = tmp[j];
		}

		return out;
	}

	static public Complex[][] getComplex(TiledImage in, int channel) {
		int width = in.getWidth();
		int height = in.getHeight();
		Complex[][] out = new Complex[height][width];
		for (int i = 0; i < height; i++)
			for (int j = 0; j < width; j++)
				out[j][i] = new Complex((float) in.getSample(i, j, channel));
		return out;
	}

	public static double norm(double oMin, double oMax, double nMin, double nMax,
			double value) {
		double ratio = (double) (nMax - nMin) / (double) (oMax - oMin);
		double result = value;
		result -= oMin;
		result *= ratio;
		result += nMin;

		return result;
	}
	
	public static int[] getMinPixelValue(TiledImage src) {
		int[] minValue = new int[src.getNumBands()];
		
		java.util.Arrays.fill(minValue, Integer.MAX_VALUE);
		
		for(int i=0; i < src.getWidth(); i++)
			for(int j=0; j < src.getHeight(); j++)
				for(int b=0; b < src.getNumBands(); b++) {
					int tmp = src.getSample(i, j, b);
					if(tmp < minValue[b])
						minValue[b] = tmp;
				}
				
		
		return minValue;
	}
	
	public static int[] getMaxPixelValue(TiledImage src) {
		int[] maxValue = new int[src.getNumBands()];
		
		java.util.Arrays.fill(maxValue, Integer.MIN_VALUE);
		
		for(int i=0; i < src.getWidth(); i++)
			for(int j=0; j < src.getHeight(); j++)
				for(int b=0; b < src.getNumBands(); b++) {
					int tmp = src.getSample(i, j, b);
					if(tmp > maxValue[b])
						maxValue[b] = tmp;
				}
				
		return maxValue;
	}
	
	static public class Complex {
		private final float re;
		private final float im;

		public Complex(float real, float imag) {
			re = real;
			im = imag;
		}

		public Complex(float real) {
			this(real, 0.0f);
		}

		public Complex() {
			this(0.0f, 0.0f);
		}

		public float abs() {
			return (float) Math.hypot(re, im);
		}

		public float phase() {
			return (float) Math.atan2(im, re);
		}

		public Complex plus(Complex b) {
			Complex a = this;
			float real = a.re + b.re;
			float imag = a.im + b.im;
			return new Complex(real, imag);
		}

		public Complex minus(Complex b) {
			Complex a = this;
			float real = a.re - b.re;
			float imag = a.im - b.im;
			return new Complex(real, imag);
		}

		public Complex times(Complex b) {
			Complex a = this;
			float real = a.re * b.re - a.im * b.im;
			float imag = a.re * b.im + a.im * b.re;
			return new Complex(real, imag);
		}

		public Complex times(float alpha) {
			return new Complex(alpha * re, alpha * im);
		}

		public float re() {
			return re;
		}

		public float im() {
			return im;
		}

		public Complex conjugate() {
			return new Complex(re, -im);
		}

		static public float[] getAbs(Complex[] in) {
			float[] out = new float[in.length];
			for (int i = 0; i < in.length; i++)
				out[i] = in[i].abs();
			return out;
		}

		static public float[] getPhase(Complex[] in) {
			float[] out = new float[in.length];
			for (int i = 0; i < in.length; i++)
				out[i] = in[i].phase();
			return out;
		}

		static public float[][] getAbs(Complex[][] in) {
			float[][] out = new float[in.length][];
			for (int i = 0; i < in.length; i++)
				out[i] = getAbs(in[i]);
			return out;
		}

		static public float[][] getPhase(Complex[][] in) {
			float[][] out = new float[in.length][];
			for (int i = 0; i < in.length; i++)
				out[i] = getPhase(in[i]);
			return out;
		}

		static public void revertQuarters(Complex[][] data) {
			int halfHeight = data.length / 2;
			int halfWidth = data[0].length / 2;

			for (int i = 0; i < halfHeight; i++)
				for (int j = 0; j < halfWidth / 2; j++) {
					// exchange second with forth quarter
					Complex tmp = data[i][j];
					data[i][j] = data[halfHeight + i][halfWidth + j];
					data[halfHeight + i][halfWidth + j] = tmp;

					// exchange first with third
					tmp = data[i][halfWidth + j];
					data[i][halfWidth + j] = data[halfHeight + i][j];
					data[halfHeight + i][j] = tmp;
				}
		}
	}
}
